]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
switch: Make handle draggable
authorDaniel Ruiz de Alegría <daniel@drasite.com>
Tue, 21 Jan 2020 10:30:42 +0000 (11:30 +0100)
committerFlorian Müllner <fmuellner@gnome.org>
Thu, 20 Jun 2024 11:30:58 +0000 (13:30 +0200)
Listen to state changes for switch menu items, since they can also be
changed without activating them now.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2717>

js/ui/popupMenu.js

index 1b52197534f99fe6f985e9b25f182701591217ca..d502a7b9e13a556c5c1c5072f493699442a71e32 100644 (file)
@@ -339,11 +339,13 @@ export const Switch = GObject.registerClass({
 }, class Switch extends St.Widget {
     _init(state) {
         this._state = false;
+        this._dragging = false;
 
         super._init({
             style_class: 'toggle-switch',
             accessible_role: Atk.Role.CHECK_BOX,
             track_hover: true,
+            reactive: true,
         });
 
         const box = new St.BoxLayout({
@@ -380,12 +382,16 @@ export const Switch = GObject.registerClass({
                 coordinate: Clutter.BindCoordinate.HEIGHT,
             }),
         });
+        this.bind_property('reactive', this._handle, 'reactive', GObject.BindingFlags.SYNC_CREATE);
+
         this._handleAlignConstraint = new Clutter.AlignConstraint({
             name: 'align',
             align_axis: Clutter.AlignAxis.X_AXIS,
             source: this,
         });
         this._handle.add_constraint(this._handleAlignConstraint);
+        this._handle.connect('button-press-event', (actor, event) => this._startDragging(event));
+        this._handle.connect('touch-event', this._touchDragging.bind(this));
         this.add_child(this._handle);
 
         this.state = state;
@@ -413,9 +419,6 @@ export const Switch = GObject.registerClass({
     }
 
     set state(state) {
-        if (this._state === state)
-            return;
-
         let handleAlignFactor;
         // Calling get_theme_node() while unmapped is an error, avoid that
         const duration = this._handle.mapped
@@ -434,13 +437,98 @@ export const Switch = GObject.registerClass({
             duration,
         });
 
-        this._state = state;
-        this.notify('state');
+        if (this._state !== state) {
+            this._state = state;
+            this.notify('state');
+        }
     }
 
     toggle() {
         this.state = !this.state;
     }
+
+    _startDragging(event) {
+        if (this._dragging)
+            return Clutter.EVENT_PROPAGATE;
+
+        this._dragging = true;
+        [this._initialGrabX] = event.get_coords();
+        let device = event.get_device();
+        let sequence = event.get_event_sequence();
+
+        this._grab = global.stage.grab(this);
+
+        this._grabbedDevice = device;
+        this._grabbedSequence = sequence;
+
+        return Clutter.EVENT_STOP;
+    }
+
+    vfunc_motion_event() {
+        if (this._dragging && !this._grabbedSequence)
+            return this._motionEvent(this, Clutter.get_current_event());
+
+        return Clutter.EVENT_PROPAGATE;
+    }
+
+    vfunc_button_release_event() {
+        if (this._dragging && !this._grabbedSequence)
+            return this._endDragging();
+
+        return Clutter.EVENT_PROPAGATE;
+    }
+
+    _touchDragging(actor, event) {
+        let sequence = event.get_event_sequence();
+
+        if (!this._dragging &&
+            event.type() === Clutter.EventType.TOUCH_BEGIN) {
+            this.startDragging(event);
+            return Clutter.EVENT_STOP;
+        } else if (this._grabbedSequence &&
+                   sequence.get_slot() === this._grabbedSequence.get_slot()) {
+            if (event.type() === Clutter.EventType.TOUCH_UPDATE)
+                return this._motionEvent(this, event);
+            else if (event.type() === Clutter.EventType.TOUCH_END)
+                return this._endDragging();
+        }
+
+        return Clutter.EVENT_PROPAGATE;
+    }
+
+    _endDragging() {
+        if (!this._dragging)
+            return Clutter.EVENT_PROPAGATE;
+
+        if (this._grab) {
+            this._grab.dismiss();
+            this._grab = null;
+        }
+
+        if (this._dragged)
+            this.state = this._handleAlignConstraint.get_factor() > 0.5;
+        else
+            this.toggle();
+
+        this._dragged = false;
+        this._grabbedSequence = null;
+        this._grabbedDevice = null;
+        this._dragging = false;
+
+        return Clutter.EVENT_STOP;
+    }
+
+    _motionEvent(actor, event) {
+        this._dragged = true;
+
+        let [absX] = event.get_coords();
+        let factorDiff = (absX - this._initialGrabX) / (this.get_width() - this._handle.get_width());
+        let factor = factorDiff + (this.state ? 1.0 : 0.0);
+
+        this._handleAlignConstraint.set_factor(Math.clamp(factor, 0, 1));
+
+        return Clutter.EVENT_STOP;
+    }
 });
 
 export const PopupSwitchMenuItem = GObject.registerClass({
@@ -454,7 +542,10 @@ export const PopupSwitchMenuItem = GObject.registerClass({
             y_expand: true,
             y_align: Clutter.ActorAlign.CENTER,
         });
+
         this._switch = new Switch(active);
+        this._switch.connect('notify::state', this._onToggled.bind(this));
+        this.bind_property('reactive', this._switch, 'reactive', GObject.BindingFlags.SYNC_CREATE);
 
         this.accessible_role = Atk.Role.CHECK_MENU_ITEM;
         this.checkAccessibleState();
@@ -506,8 +597,6 @@ export const PopupSwitchMenuItem = GObject.registerClass({
 
     toggle() {
         this._switch.toggle();
-        this.emit('toggled', this._switch.state);
-        this.checkAccessibleState();
     }
 
     get state() {
@@ -519,6 +608,11 @@ export const PopupSwitchMenuItem = GObject.registerClass({
         this.checkAccessibleState();
     }
 
+    _onToggled(sw, state) {
+        this.emit('toggled', state);
+        this.checkAccessibleState();
+    }
+
     checkAccessibleState() {
         switch (this.accessible_role) {
         case Atk.Role.CHECK_MENU_ITEM: